
#include "aerFreeCamera.hpp"

#include <glm/gtx/euler_angles.hpp> // for yawPitchRoll
#include "../app/aerEventHandler.hpp"


namespace aer {

FreeCamera::FreeCamera(const View &view, const Frustum &frustum)
  : Camera(view, frustum)
{
  updateEulerAngles();
  
  m_moveCoef = 0.5f;
  m_rotationCoef = 0.01f;
  m_inertiaCoef = 0.85f;
  
  m_bLimitPitchAngle = true;
  m_bInvertPitch = false;
  m_bInvertYaw = false;

  m_cursorDelta = Vector2(0.0f);
  
  m_bEnableRotation = true;
  m_bEnableMove     = true;
  
  m_bUseJoystick = true;
}

void FreeCamera::update()
{
  updateMotion();
  updateRotation();

  // Compute the rotation matrix
  // Ry(yaw) * Rx(pitch) * Rz(0.f)
  glm::mat3 cameraRotate = glm::mat3( glm::yawPitchRoll( m_yawAngle, m_pitchAngle, 0.0f) );
    
  
  const Vector3 front( 0.0f, 0.0f, -1.0f);
  Vector3 direction = glm::normalize( cameraRotate * front );
  
  const Vector3 left( -1.0f, 0.0f, 0.0f);
  glm::vec3 worldLeft = glm::normalize( cameraRotate * left );
  
  // .Compute the new view parameters
  Vector3 position = getPosition() + cameraRotate * m_moveVelocity;  
  Vector3 target   = position + direction;  
  Vector3 up       = glm::cross( direction, worldLeft);
  
  m_view.set( position, target, up);
  build();  
  
  /**/
  
  //Having the camera model matrix can be helpful + it holds position / target & up in
  // its columns
  //camera = view⁻¹  
}

void FreeCamera::updateMotion()
{
  Vector3 direction(0.0f);
  
  // == Joystick ==
  if (m_bUseJoystick)
  {
    direction.x = EventHandler::GetJoystickAxisPosition(0);
    direction.z = EventHandler::GetJoystickAxisPosition(1);
  }
  
  
  // == Keyboard ==
  if (EventHandler::IsKeyDown(sf::Keyboard::D))        direction.x += 1.0f;
  if (EventHandler::IsKeyDown(sf::Keyboard::Q))        direction.x -= 1.0f;
  if (EventHandler::IsKeyDown(sf::Keyboard::PageUp))   direction.y += 1.0f;
  if (EventHandler::IsKeyDown(sf::Keyboard::PageDown)) direction.y -= 1.0f;
  if (EventHandler::IsKeyDown(sf::Keyboard::S))        direction.z += 1.0f;
  if (EventHandler::IsKeyDown(sf::Keyboard::Z))        direction.z -= 1.0f;
  
  if ((direction.x != 0.0f) || (direction.y != 0.0f) || (direction.z != 0.0f)) {
    direction = glm::normalize(direction);
  }
  
  m_moveVelocity = m_moveCoef * direction;
}

void FreeCamera::updateRotation()
{
/// Note: Euler angles could be out of date...
  
  if (!m_bEnableRotation) return;  
  
  float inertia = m_rotationVelocity.x * m_rotationVelocity.x +
                  m_rotationVelocity.y * m_rotationVelocity.y;
      
  bool bHasRotated = false;
  Vector2 newDelta(0.0f);
  
  
  // == Joystick ==
  if (m_bUseJoystick)
  {
    const float coefJoystick = 4.5f;//
    newDelta.x = coefJoystick * EventHandler::GetJoystickAxisPosition(2);
    newDelta.y = coefJoystick * EventHandler::GetJoystickAxisPosition(3);
    
    if ((newDelta.x != 0.0f) || (newDelta.y != 0.0f)) {
      bHasRotated = true;
    }
  }
  
      
  if (EventHandler::IsMouseButtonReleased(sf::Mouse::Left)) {
    m_cursorDelta = glm::vec2(0.0f);
  }
  
  if (EventHandler::IsMouseButtonDown(sf::Mouse::Left))
  { 
    bHasRotated = true;
    newDelta = EventHandler::GetMouseDelta();
  }

  /// Update camera rotation
  if (bHasRotated || (inertia > FLT_EPSILON))
  {
    if (bHasRotated)
    {
      // interpolate to avoid jaggies
      m_cursorDelta = 0.70f*m_cursorDelta + 0.30f*newDelta;
      m_rotationVelocity = m_rotationCoef * m_cursorDelta;
    }
    else
    {
      // if the camera stop to move, add inertia.
      m_rotationVelocity *= m_inertiaCoef;
    }
    
    float yawDelta = m_rotationVelocity.x;
    m_yawAngle += (m_bInvertYaw)? yawDelta : -yawDelta;
    
    float pitchDelta = m_rotationVelocity.y;
    m_pitchAngle += (m_bInvertPitch)? pitchDelta : -pitchDelta;
    
    if (m_bLimitPitchAngle)
    {
      m_pitchAngle = std::max( m_pitchAngle, float(-M_PI_2));
      m_pitchAngle = std::min( m_pitchAngle, float(+M_PI_2));
    }
  }
}

void FreeCamera::updateEulerAngles()
{
  /// Retrieve the yaw & pitch angle  
  glm::vec3 zAxis = -getDirection();  // (it's also the third row of the viewMatrix)
  
  m_yawAngle = atan2f( zAxis.x, zAxis.z);
      
  float len = sqrtf( zAxis.x*zAxis.x + zAxis.z*zAxis.z );
  m_pitchAngle = - atan2f( zAxis.y, len);
}

} // aer

